/*
 * Created by SharpDevelop.
 * User: phtrung
 * Date: 9/26/2008
 * Time: 3:12 PM
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */

using System;
using System.Data;
using System.Collections;
using System.IO;
using System.Windows.Forms;

namespace popLib
{
	/// <summary>
	/// Description of DataCls.
	/// </summary>
	public class DataLib
	{
		public enum CopyOption{ StartIndex, EndIndex, IndexesSrc, IndexesDes, NamesSrc, NamesDes, IsMatchColumnName, IsOverwritePK }
		
		
		public static DataTable initDataTable(string tableInfo){
			
			string columnsStr = popLib.StringLib.subStringBetween( tableInfo, "[", "]", 0 );			
			DataTable result  = new DataTable( tableInfo.Replace( "["+columnsStr+"]", "" ).Trim() );
							
			string[] columnArr = StringLib.splitByAny( columnsStr, "|,;" );		
			ArrayList PrimaryKeys = new ArrayList();
			
			foreach( string column in columnArr ){				
				Type columnType = Type.GetType("System.String");
				if( column.IndexOf("@") >= 0 ) columnType = Type.GetType("System.DateTime");
				if( column.IndexOf("%") >= 0 ) columnType = Type.GetType("System.Double");
				if( column.IndexOf("$") >= 0 ) columnType = Type.GetType("System.Int32");					
				result.Columns.Add( column.Replace("#","").Replace("%","").Replace("@","").Replace("$","").Trim(), columnType );
				
				//check if column name have # will add to arraylist PrimaryKeys
				if( column.IndexOf("#") >= 0 )
					PrimaryKeys.Add( result.Columns[ result.Columns.Count - 1 ] );
									
			}
			
			if(PrimaryKeys.Count > 0){
				DataColumn[] PrimaryKeyCols = new DataColumn[PrimaryKeys.Count];
				PrimaryKeys.CopyTo( PrimaryKeyCols );
				result.PrimaryKey = PrimaryKeyCols;
			}
			
			return result;
		}
		
		/// <summary>
		/// Create dataset structure follow the format: 
		/// table1[col1|...] \r\n table2[col1|...]
		/// if colname include character [ # ] at the begining => primarykey
		/// if colname include character [ @ ] at the begining => datetime type
		/// if colname include character [ $ ] at the begining => int type
		/// if colname include character [ % ] at the begining => double type
		/// otherwise => string type
		/// Note: 
		/// "\r\n" can be replaced by "&"
		/// "|" can be replaced by "," or ";"
		/// </summary>
		/// <param name="tableInfo"></param>
		/// <returns></returns>
		public static DataSet initDataSet(string tableInfo){
			string[] tableArr = StringLib.splitByAny( tableInfo, new string[]{ "\r\n" , "&" } );
			DataSet result = new DataSet("DocumentElement");
			
			//create each table
			foreach( string table in tableArr ){		
				result.Tables.Add( initDataTable(table) );
			}		
			
			return result;
		}
				
		
		/// <summary>
		/// add new row to des table from Datarow
		/// options is format like: optionName:value\r\n optionName:value
		/// </summary>
		/// <param name="table"></param>
		/// <param name="row"></param>
		/// <param name="optionStr">IsOverwritePK: overwrite the row has same PK value, value: 1/0 
		/// StartIndex/EndIndex: indicate column index range to copy, Ex: "fromIndex=0&toIndex=2"
		/// IndexesSrc/IndexesDes: indicate what column index to copy from and to, Ex: "fromColumnIndexes=0,1&toColumnIndexes=2,3" 
		/// NamesSrc/NamesDes: indicate what column name to copy from and to, Ex: "fromColumnName=Age,Name&toColumnName=Age,Fullname"
		/// IsMatchColumnName: if copy all columns, copy columns have the same name only, value = 1/0
		/// </param>
		public static void addNewRow(DataTable table, DataRow row, string optionStr){
			
			Hashtable options = StringLib.parseOption( optionStr );
			DataRow newRow = table.NewRow();
			
			//overwrite row has same PK value
			bool overwriteRow = (options.ContainsKey("IsOverwritePK") && options["IsOverwritePK"].ToString() == "1") ? true : false;			
			DataRow similarRow = findRowSamePrimaryKey( table, row );
			
			
			if( similarRow == null || ( similarRow != null && overwriteRow ) ){
				
				//REMOVE similar row
				if( similarRow != null ) table.Rows.Remove(similarRow);
				
				updateRow( newRow, row, optionStr );
				
				table.Rows.Add(newRow);
			}
		}
		
		public static void addNewRow(DataTable table, DataRow[] rows, string optionStr){
			foreach( DataRow row in rows)
				addNewRow( table, row, optionStr );			
		}
		
		
		/// <summary>
		/// Update data from source row to destination rows
		/// </summary>
		/// <param name="desRow"></param>
		/// <param name="srcRow"></param>
		/// <param name="optionStr">
		/// StartIndex/EndIndex: indicate column index range to copy, Ex: "fromIndex=0&toIndex=2"
		/// IndexesSrc/IndexesDes: indicate what column index to copy from and to, Ex: "fromColumnIndexes=0,1&toColumnIndexes=2,3" 
		/// NamesSrc/NamesDes: indicate what column name to copy from and to, Ex: "fromColumnName=Age,Name&toColumnName=Age,Fullname"
		/// IsMatchColumnName: if copy all columns, copy columns have the same name only, value = 1/0
		/// </param>
		public static void updateRow( DataRow desRow, DataRow srcRow, string optionStr  ){
			Hashtable options = StringLib.parseOption( optionStr );
			DataTable  table  = desRow.Table;
			
			int fromIndex = options.ContainsKey("StartIndex") ? Convert.ToInt32(options["StartIndex"]) : 0 ;
			int toIndex = options.ContainsKey("EndIndex") ? Convert.ToInt32(options["EndIndex"]) : table.Columns.Count ;
			
			string fromColumnIndexes = options.ContainsKey("IndexesSrc") ? options["IndexesSrc"].ToString()	: "" ;			
			string toColumnIndexes   = options.ContainsKey("IndexesDes") ? options["IndexesDes"].ToString()	: "" ;
			
			string fromColumnNames 	 = options.ContainsKey("NamesSrc") 	? options["NamesSrc"].ToString() 	: "" ;
			string toColumnNames   	 = options.ContainsKey("NamesDes") 	? options["NamesDes"].ToString() 		: "" ;
			
			bool matchColumnName = options.ContainsKey("IsMatchColumnName") ? options["IsMatchColumnName"].ToString()=="1" ? true : false : false ;
			
			if(fromColumnNames.Trim().Length > 0 && toColumnNames.Trim().Length > 0){
				string[] fromNameArr = StringLib.splitByAny( fromColumnNames, new string[]{ ",",";","|" } );
				string[] toNameArr   = StringLib.splitByAny( toColumnNames, new string[]{ ",",";","|" } );
				
				for( int i=0 ;i< toNameArr.Length; i++)		
					if( table.Columns.IndexOf( toNameArr[i].Trim() ) >= 0 && srcRow.Table.Columns.IndexOf( fromNameArr[i].Trim() ) >= 0 )
						desRow[ toNameArr[i].Trim() ] = srcRow[ fromNameArr[i].Trim() ];
				
			}else if(fromColumnIndexes.Trim().Length > 0 && toColumnIndexes.Trim().Length > 0){
				string[] fromIndexArr = StringLib.splitByAny( fromColumnNames, new string[]{ ",",";","|" } );
				string[] toIndexArr   = StringLib.splitByAny( toColumnNames, new string[]{ ",",";","|" } );
				
				for( int i=0 ;i< toIndexArr.Length; i++)
					if( table.Columns.Count > Convert.ToInt32( toIndexArr[i] ) && 
					   				srcRow.Table.Columns.Count > Convert.ToInt32( fromIndexArr[i] ) )
					desRow[ Convert.ToInt32(toIndexArr[i]) ] = srcRow[ Convert.ToInt32(fromIndexArr[i]) ];
					
			}else if(matchColumnName){
				foreach( DataColumn col in table.Columns )
					if( srcRow.Table.Columns.IndexOf(col.ColumnName) >= 0 )
						desRow[col.ColumnName] = srcRow[col.ColumnName];
			}else {
				for( int i=fromIndex ;i< toIndex; i++)
					if(srcRow.Table.Columns.Count > i)
						desRow[i] = srcRow[i];
			}
		}
				
		/// <summary>
		/// Copy all rows form source table to destination table.
		/// </summary>
		/// <param name="destinationTable"></param>
		/// <param name="sourceTable"></param>
		/// <param name="optionStr">IsOverwritePK: overwrite the row has same PK value, value: 1/0
		/// StartIndex/EndIndex: indicate column index range to copy, Ex: "fromIndex=0&toIndex=2"
		/// IndexesSrc/IndexesDes: indicate what column index to copy from and to, Ex: "fromColumnIndexes=0,1&toColumnIndexes=2,3" 
		/// NamesSrc/NamesDes: indicate what column name to copy from and to, Ex: "fromColumnName=Age,Name&toColumnName=Age,Fullname"
		/// IsMatchColumnName: if copy all columns, copy columns have the same name only, value = 1/0
		/// </param>
		public static void addRowsFromTable(DataTable desTable, DataTable srcTable, string optionStr){
			foreach( DataRow row in srcTable.Rows )
				addNewRow( desTable, row, optionStr );
		}
		
		//return the datarow that has same pk value with the row
		public static DataRow findRowSamePrimaryKey( DataTable table, DataRow row  ){
			try{
				DataColumn[] rowColKeys   = row.Table.PrimaryKey;
				DataColumn[] TableColKeys = table.PrimaryKey;
				
												
				string query = "";
				foreach( DataColumn col in TableColKeys )				
					query += string.Format( "{0} = '{1}' AND " , col.ColumnName, row[col.ColumnName].ToString() );
				
				query += "AND";
				query = query.Replace( "AND AND", "" );
				
				
				DataRow[] result = table.Select(query);
				
				return result.Length > 0 ? result[0] : null ;
				
			}catch(Exception ex){ex.ToString();}
			return null;
		}
		
		/// <summary>
		/// check if XML is corrupted.
		/// </summary>
		/// <param name="xmlFile"></param>
		/// <returns></returns>
		public static bool checkXML(string xmlFile){			
			DataSet result = new DataSet();
			try{
				if(File.Exists(xmlFile)){
					result.ReadXml(xmlFile);
					return true;
				}
			}catch(Exception ex){ex.ToString();}
			return false;
		}
		
		/// <summary>
		/// check if XML is corrupted.
		/// if ok, read xml otherwise read xml as much as possible.
		/// Note: best result for xml that has at most 3 level.
		/// </summary>
		/// <param name="xmlFile"></param>
		/// <param name="xmlDB">string describes dataset structure. Ex: TABLE[col1, col2...]...</param>
		/// <returns></returns>
		public static DataSet readXMLFile(string xmlFile, string xmlDB){
			DataSet result = xmlDB != null ? DataLib.initDataSet(xmlDB) : new DataSet();
			
			bool xmlError = !checkXML( xmlFile );
			
			try{
				if(xmlError){
					result = xmlDB != null ? DataLib.initDataSet(xmlDB) : new DataSet();		
					string fileContent = FileLib.readFile( xmlFile );
					
					string firstPart 	= StringLib.getListGroupString( fileContent, "<?xml", "</" )[0].ToString();
					string secondPart 	= StringLib.splitByAny( firstPart, new string[]{"\r\n"} )[2];
					firstPart 			= StringLib.splitByAny( firstPart, new string[]{"\r\n"} )[1];			
									
					string undamagedPart = fileContent.Substring( 0, fileContent.LastIndexOf(secondPart.Replace("<","</")) );
					undamagedPart		= undamagedPart + "\r\n\t" + secondPart.Replace("<","</") + "\r\n" + firstPart.Replace("<","</");
						   
					string destFile = "temp.xml";
					FileLib.writeFile( destFile, undamagedPart );
					
					result = readXMLFile(destFile, xmlDB);
				}
			}catch(Exception ex){string abc = ex.ToString();}
			
			return result;
		}
		
		
		/// <summary>
		/// remove row which match the condition
		/// 
		/// </summary>
		/// <param name="table"></param>
		/// <param name="condition">		
		/// colName = value, #colName > value, $colName \<= value
		/// Note:
		///   $colName: this column is Number type
		///   #colName: this column is DateTime type
		///   colName : this column is string
		///   "," can be replaced by ";", "|"
		/// </param>
		public static void removeRows(DataTable table, string conditionStr ){
			string[] conditionArr = StringLib.splitByAny( conditionStr, "|,;");
			
			Hashtable consHtbl = new Hashtable(); // key - value
			Hashtable comsHtbl = new Hashtable(); // key - compare type ( =, >, <, >=, <=, != )
			//save conditions to hastable
			foreach( string condition in conditionArr ){
				string[] parts = StringLib.splitByAny( condition, new string[]{ "=", ">", "<", ">=", "<=" , "!=" } );
				consHtbl.Add( parts[0].Trim(), parts[1].Trim().Replace("'","") );
				comsHtbl.Add( parts[0].Trim(), condition.Replace( parts[0], "" ).Replace(parts[1], "").Trim() );
			}
			
			
			for( int i = 0; i < table.Rows.Count ;i++ ){
				DataRow row = table.Rows[i];
				int matched = 0;
				foreach( string key in consHtbl.Keys ){					
					string columnType  = StringLib.filterStr( key, "#$").Trim();
					string compareType = comsHtbl[key].ToString();
					string realKey     = key.Replace("$","").Replace("#","");
					
					if( table.Columns.IndexOf(realKey) >= 0 ){
						//compare with column value.
						string result = "";
						DateTimeLib.CompareType compare;
						switch( columnType ){
							case "#": 
								compare = DateTimeLib.compareDate( row[realKey].ToString(), consHtbl[key].ToString() );
								result = "=";
								if( compare == DateTimeLib.CompareType.lower ) result = "<";
								if( compare == DateTimeLib.CompareType.higher ) result = ">";
								break;
							case "$":
								compare = NumberLib.compareNumber( row[realKey].ToString(), consHtbl[key].ToString() );
								result = "=";
								if( compare == DateTimeLib.CompareType.lower ) result = "<";
								if( compare == DateTimeLib.CompareType.higher ) result = ">";
								break;
							default: result = row[realKey].ToString() == consHtbl[key].ToString() ? "=" : "!="; break;								
						}
						
						
						//validate with condition
						if( compareType == result ){ matched++; }
						else if( compareType == "!=" && result != "=" ){ matched++;  }
						else if( compareType != "!=" && compareType.Length == 2 && StringLib.filterStr( compareType, result ) != "" ){ matched++; }
					}
				}
				
				if( matched == consHtbl.Count ){
					table.Rows.Remove( row );
					i--;
				}				
				
			}		
		}
		
		
		/// <summary>
		///set width to multi columns	
		///widthList: width list 
		///ex: 0:45|1:75|2-4:120|5,8,12:200
		///0:45 		 -> column 0 width 45
		///2-4:120    -> column from 2 to 4 width 120
		///5,8,12:200 -> column 5, 8 and 12 width 200
		/// </summary>
		public static void setGridColumnStyle(DataGrid dg, string widthList)
		{
			if(dg.TableStyles.Count == 0){
				DataGridTableStyle dgts = new DataGridTableStyle();
				dgts.MappingName = ((DataSet)dg.DataSource).Tables[0].TableName;		
				dg.TableStyles.Add(dgts);
			}
			
			if(widthList!=null)
			{			
				string[] arrayStr = widthList.Split('|');			
				
				for(int i=0;i<arrayStr.Length;i++){
					if(arrayStr[i].IndexOf("-") >= 0){
						int from  = Convert.ToInt16( arrayStr[i].Split(':')[0].Split('-')[0] );
						int to    = Convert.ToInt16( arrayStr[i].Split(':')[0].Split('-')[1] );
						int width = Convert.ToInt16( arrayStr[i].Split(':')[1] );
						for( int j=from; j<= to; j++ )
							dg.TableStyles[0].GridColumnStyles[ j ].Width = width;					
					}else if(arrayStr[i].IndexOf(",") >= 0){
						int width = Convert.ToInt16( arrayStr[i].Split(':')[1] );
						string[] arrIndex = arrayStr[i].Split(':')[0].Split(',');
						foreach( string index in arrIndex)
							dg.TableStyles[0].GridColumnStyles[ Convert.ToInt16(index) ].Width = width;
					}else			
						dg.TableStyles[0].GridColumnStyles[ Convert.ToInt16( arrayStr[i].Split(':')[0] ) ].Width = Convert.ToInt16( arrayStr[i].Split(':')[1] );
				}
				
			}
		}
		
		public static DataTable parseTextToTable(string text, string tableInfo, bool removeSpace){
			DataTable table = initDataTable( tableInfo );
			parseTextToTable( text, table, removeSpace );			
			return table;
		}
		
		public static void parseTextToTable(string text, DataTable table, bool removeSpace){
			string[] lines = StringLib.splitByAny( text, "\r\n,&".Split(',') );
			
			foreach( string line in lines ){
				if( StringLib.isValid( line ) ){
					string[] cols = StringLib.splitByAny( line, ";,|" );
					DataRow row = table.NewRow();				
					for( int i=0;i<table.Columns.Count;i++ )
						if( i < cols.Length ) row[i] = removeSpace ? cols[i].Trim() : cols[i];
					
					table.Rows.Add( row );
				}
			}		
		}
		
	}
}
